UART
Specification
This UART controller tutorial is based on this implementation.
This implementation is characterized by:
ClockDivider/Parity/StopBit/DataLength configs are set by the component inputs.
RXD input is filtered by using a sampling window of N samples and a majority vote.
Interfaces of this UartCtrl are:
Name |
Type |
Description |
---|---|---|
config |
UartCtrlConfig |
Give all configurations to the controller |
write |
Stream[Bits] |
Port used by the system to give transmission order to the controller |
read |
Flow[Bits] |
Port used by the controller to notify the system about a successfully received frame |
uart |
Uart |
Uart interface with rxd / txd |
Data structures
Before implementing the controller itself we need to define some data structures.
Controller construction parameters
Name |
Type |
Description |
---|---|---|
dataWidthMax |
Int |
Maximum number of data bits that could be sent using a single UART frame |
clockDividerWidth |
Int |
Number of bits that the clock divider has |
preSamplingSize |
Int |
Number of samples to drop at the beginning of the sampling window |
samplingSize |
Int |
Number of samples use at the middle of the window to get the filtered RXD value |
postSamplingSize |
Int |
Number of samples to drop at the end of the sampling window |
To make the implementation easier let’s assume that preSamplingSize + samplingSize + postSamplingSize
is always a power of two.
If so we can skip resetting counters in a few places.
Instead of adding each construction parameters (generics) to UartCtrl
one by one, we can group them inside a class that will be used as single parameter of UartCtrl
.
case class UartCtrlGenerics(dataWidthMax: Int = 8,
clockDividerWidth: Int = 20, // baudrate = Fclk / rxSamplePerBit / clockDividerWidth
preSamplingSize: Int = 1,
samplingSize: Int = 5,
postSamplingSize: Int = 2) {
val rxSamplePerBit = preSamplingSize + samplingSize + postSamplingSize
assert(isPow2(rxSamplePerBit))
if((samplingSize % 2) == 0)
SpinalWarning(s"It's not nice to have a odd samplingSize value (because of the majority vote)")
}
UART interface
Let’s define a UART interface bundle without flow control.
case class Uart() extends Bundle with IMasterSlave {
val txd = Bool()
val rxd = Bool()
override def asMaster(): Unit = {
out(txd)
in(rxd)
}
}
UART configuration enums
Let’s define parity and stop bit enumerations.
object UartParityType extends SpinalEnum(binarySequential) {
val NONE, EVEN, ODD = newElement()
}
object UartStopType extends SpinalEnum(binarySequential) {
val ONE, TWO = newElement()
def toBitCount(that: C): UInt = (that === ONE) ? U"0" | U"1"
}
UartCtrl configuration Bundles
Let’s define bundles that will be used as IO elements to setup UartCtrl
.
case class UartCtrlFrameConfig(g: UartCtrlGenerics) extends Bundle {
val dataLength = UInt(log2Up(g.dataWidthMax) bits) // Bit count = dataLength + 1
val stop = UartStopType()
val parity = UartParityType()
}
case class UartCtrlConfig(g: UartCtrlGenerics) extends Bundle {
val frame = UartCtrlFrameConfig(g)
val clockDivider = UInt(g.clockDividerWidth bits) // see UartCtrlGenerics.clockDividerWidth for calculation
def setClockDivider(baudrate: Double, clkFrequency: HertzNumber = ClockDomain.current.frequency.getValue): Unit = {
clockDivider := (clkFrequency.toDouble / baudrate / g.rxSamplePerBit).toInt
}
}
Implementation
In UartCtrl
, 3 things will be instantiated:
One clock divider that generates a tick pulse at the UART RX sampling rate.
One
UartCtrlTx
componentOne
UartCtrlRx
component
UartCtrlTx
The interfaces of this Component
are the following :
Name |
Type |
Description |
---|---|---|
configFrame |
UartCtrlFrameConfig |
Contains data bit width count and party/stop bits configurations |
samplingTick |
Bool |
Time reference that pulses |
write |
Stream[Bits] |
Port used by the system to give transmission orders to the controller |
txd |
Bool |
UART txd pin |
Let’s define the enumeration that will be used to store the state of UartCtrlTx
:
object UartCtrlTxState extends SpinalEnum {
val IDLE, START, DATA, PARITY, STOP = newElement()
}
Let’s define the skeleton of UartCtrlTx
:
class UartCtrlTx(g : UartCtrlGenerics) extends Component {
import g._
val io = new Bundle {
val configFrame = in(UartCtrlFrameConfig(g))
val samplingTick = in Bool()
val write = slave Stream (Bits(dataWidthMax bits))
val txd = out Bool()
}
// Provide one clockDivider.tick each rxSamplePerBit pulses of io.samplingTick
// Used by the stateMachine as a baud rate time reference
val clockDivider = new Area {
val counter = Reg(UInt(log2Up(rxSamplePerBit) bits)) init(0)
val tick = False
..
}
// Count up each clockDivider.tick, used by the state machine to count up data bits and stop bits
val tickCounter = new Area {
val value = Reg(UInt(Math.max(dataWidthMax, 2) bits))
def reset() = value := 0
..
}
val stateMachine = new Area {
import UartCtrlTxState._
val state = RegInit(IDLE)
val parity = Reg(Bool())
val txd = True
..
switch(state) {
..
}
}
io.txd := RegNext(stateMachine.txd) init(True)
}
And here is the complete implementation:
class UartCtrlTx(g : UartCtrlGenerics) extends Component {
import g._
val io = new Bundle {
val configFrame = in(UartCtrlFrameConfig(g))
val samplingTick = in Bool()
val write = slave Stream Bits(dataWidthMax bits)
val txd = out Bool()
}
// Provide one clockDivider.tick each rxSamplePerBit pulse of io.samplingTick
// Used by the stateMachine as a baudrate time reference
val clockDivider = new Area {
val counter = Reg(UInt(log2Up(rxSamplePerBit) bits)) init 0
val tick = False
when(io.samplingTick) {
counter := counter - 1
tick := counter === 0
}
}
// Count up each clockDivider.tick, used by the state machine to count up data bits and stop bits
val tickCounter = new Area {
val value = Reg(UInt(log2Up(Math.max(dataWidthMax, 2)) bits))
def reset(): Unit = value := 0
when(clockDivider.tick) {
value := value + 1
}
}
val stateMachine = new Area {
import UartCtrlTxState._
val state = RegInit(IDLE)
val parity = Reg(Bool())
val txd = True
when(clockDivider.tick) {
parity := parity ^ txd
}
io.write.ready := False
switch(state) {
is(IDLE) {
when(io.write.valid && clockDivider.tick) {
state := START
}
}
is(START) {
txd := False
when(clockDivider.tick) {
state := DATA
parity := io.configFrame.parity === UartParityType.ODD
tickCounter.reset()
}
}
is(DATA) {
txd := io.write.payload(tickCounter.value)
when(clockDivider.tick) {
when(tickCounter.value === io.configFrame.dataLength) {
io.write.ready := True
tickCounter.reset()
when(io.configFrame.parity === UartParityType.NONE) {
state := STOP
} otherwise {
state := PARITY
}
}
}
}
is(PARITY) {
txd := parity
when(clockDivider.tick) {
state := STOP
tickCounter.reset()
}
}
is(STOP) {
when(clockDivider.tick) {
when(tickCounter.value === UartStopType.toBitCount(io.configFrame.stop)) {
state := io.write.valid ? START | IDLE
}
}
}
}
}
io.txd := RegNext(stateMachine.txd, True)
}
UartCtrlRx
The interfaces of this Component
are the following:
Name |
Type |
Description |
---|---|---|
configFrame |
UartCtrlFrameConfig |
Contains data bit width and party/stop bits configurations |
samplingTick |
Bool |
Time reference that pulses |
read |
Flow[Bits] |
Port used by the controller to notify the system about a successfully received frame |
rxd |
Bool |
UART rxd pin, not synchronized with the current clock domain |
Let’s define the enumeration that will be used to store the state of UartCtrlTx
:
object UartCtrlRxState extends SpinalEnum {
val IDLE, START, DATA, PARITY, STOP = newElement()
}
Let’s define the skeleton of the UartCtrlRx :
class UartCtrlRx(g : UartCtrlGenerics) extends Component {
import g._
val io = new Bundle {
val configFrame = in(UartCtrlFrameConfig(g))
val samplingTick = in Bool()
val read = master Flow (Bits(dataWidthMax bits))
val rxd = in Bool()
}
// Implement the rxd sampling with a majority vote over samplingSize bits
// Provide a new sampler.value each time sampler.tick is high
val sampler = new Area {
val syncroniser = BufferCC(io.rxd)
val samples = History(that=syncroniser,when=io.samplingTick,length=samplingSize)
val value = RegNext(MajorityVote(samples))
val tick = RegNext(io.samplingTick)
}
// Provide a bitTimer.tick each rxSamplePerBit
// reset() can be called to recenter the counter over a start bit.
val bitTimer = new Area {
val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
def reset() = counter := preSamplingSize + (samplingSize - 1) / 2 - 1)
val tick = False
...
}
// Provide bitCounter.value that count up each bitTimer.tick, Used by the state machine to count data bits and stop bits
// reset() can be called to reset it to zero
val bitCounter = new Area {
val value = Reg(UInt(Math.max(dataWidthMax, 2) bits))
def reset() = value := 0
...
}
val stateMachine = new Area {
import UartCtrlRxState._
val state = RegInit(IDLE)
val parity = Reg(Bool())
val shifter = Reg(io.read.payload)
...
switch(state) {
...
}
}
}
And here is the complete implementation:
class UartCtrlRx(g : UartCtrlGenerics) extends Component {
import g._
val io = new Bundle {
val configFrame = in(UartCtrlFrameConfig(g))
val samplingTick = in Bool()
val read = master Flow Bits(dataWidthMax bits)
val rxd = in Bool()
}
// Implement the rxd sampling with a majority vote over samplingSize bits
// Provide a new sampler.value each time sampler.tick is high
val sampler = new Area {
val synchronizer = BufferCC(io.rxd)
val samples = spinal.lib.History(that=synchronizer, when=io.samplingTick, length=samplingSize)
val value = RegNext(MajorityVote(samples))
val tick = RegNext(io.samplingTick)
}
// Provide a bitTimer.tick each rxSamplePerBit
// reset() can be called to recenter the counter over a start bit.
val bitTimer = new Area {
val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
def reset(): Unit = counter := preSamplingSize + (samplingSize - 1) / 2 - 1
val tick = False
when(sampler.tick) {
counter := counter - 1
tick := counter === 0
}
}
// Provide bitCounter.value that count up each bitTimer.tick, Used by the state machine to count data bits and stop bits
// reset() can be called to reset it to zero
val bitCounter = new Area {
val value = Reg(UInt(log2Up(Math.max(dataWidthMax, 2)) bits))
def reset(): Unit = value := 0
when(bitTimer.tick) {
value := value + 1
}
}
val stateMachine = new Area {
import UartCtrlRxState._
val state = RegInit(IDLE)
val parity = Reg(Bool())
val shifter = Reg(io.read.payload)
// Parity calculation
when(bitTimer.tick) {
parity := parity ^ sampler.value
}
io.read.valid := False
switch(state) {
is(IDLE) {
when(!sampler.value) {
state := START
bitTimer.reset()
bitCounter.reset()
}
}
is(START) {
when(bitTimer.tick) {
state := DATA
bitCounter.reset()
parity := io.configFrame.parity === UartParityType.ODD
when(sampler.value) {
state := IDLE
}
}
}
is(DATA) {
when(bitTimer.tick) {
shifter(bitCounter.value) := sampler.value
when(bitCounter.value === io.configFrame.dataLength) {
bitCounter.reset()
when(io.configFrame.parity === UartParityType.NONE) {
state := STOP
} otherwise {
state := PARITY
}
}
}
}
is(PARITY) {
when(bitTimer.tick) {
state := STOP
bitCounter.reset()
when(parity =/= sampler.value) {
state := IDLE
}
}
}
is(STOP) {
when(bitTimer.tick) {
when(!sampler.value) {
state := IDLE
}.elsewhen(bitCounter.value === UartStopType.toBitCount(io.configFrame.stop)) {
state := IDLE
io.read.valid := True
}
}
}
}
}
io.read.payload := stateMachine.shifter
}
UartCtrl
Let’s write UartCtrl
that instantiates the UartCtrlRx
and UartCtrlTx
parts, generate the clock divider logic, and connect them to each other.
class UartCtrl(g: UartCtrlGenerics=UartCtrlGenerics()) extends Component {
val io = new Bundle {
val config = in(UartCtrlConfig(g))
val write = slave(Stream(Bits(g.dataWidthMax bits)))
val read = master(Flow(Bits(g.dataWidthMax bits)))
val uart = master(Uart())
}
val tx = new UartCtrlTx(g)
val rx = new UartCtrlRx(g)
// Clock divider used by RX and TX
val clockDivider = new Area {
val counter = Reg(UInt(g.clockDividerWidth bits)) init 0
val tick = counter === 0
counter := counter - 1
when(tick) {
counter := io.config.clockDivider
}
}
tx.io.samplingTick := clockDivider.tick
rx.io.samplingTick := clockDivider.tick
tx.io.configFrame := io.config.frame
rx.io.configFrame := io.config.frame
tx.io.write << io.write
rx.io.read >> io.read
io.uart.txd <> tx.io.txd
io.uart.rxd <> rx.io.rxd
}
To make it easier to use the UART with fixed settings, we introduce an companion object for UartCtrl
. It allows us to provide
additional ways of instantiating a UartCtrl component with different sets of parameters. Here we define a UartCtrlInitConfig
holding the settings for a component that is not runtime configurable. Note that it is still possible to instantiate the UartCtrl
manually like all other components, which one would do if a runtime-configurable UART is needed (via val uart = new UartCtrl()
).
case class UartCtrlInitConfig(baudrate: Int = 0,
dataLength: Int = 1,
parity: UartParityType.E = null,
stop: UartStopType.E = null
) {
require(dataLength >= 1)
def initReg(reg : UartCtrlConfig): Unit = {
require(reg.isReg)
if(baudrate != 0) reg.clockDivider init((ClockDomain.current.frequency.getValue / baudrate / reg.g.rxSamplePerBit).toInt-1)
if(dataLength != 1) reg.frame.dataLength init (dataLength - 1)
if(parity != null) reg.frame.parity init parity
if(stop != null) reg.frame.stop init stop
}
}
object UartCtrl {
def apply(config: UartCtrlInitConfig, readonly: Boolean = false): UartCtrl = {
val uartCtrl = new UartCtrl()
uartCtrl.io.config.setClockDivider(config.baudrate)
uartCtrl.io.config.frame.dataLength := config.dataLength - 1
uartCtrl.io.config.frame.parity := config.parity
uartCtrl.io.config.frame.stop := config.stop
if (readonly) {
uartCtrl.io.write.valid := False
uartCtrl.io.write.payload := B(0)
}
uartCtrl
}
}
Simple usage
To synthesize a UartCtrl
as 115200-N-8-1
:
val uartCtrl = UartCtrl(
config=UartCtrlInitConfig(
baudrate = 115200,
dataLength = 7,
parity = UartParityType.NONE,
stop = UartStopType.ONE
)
)
If you are using txd
pin only, add:
uartCtrl.io.uart.rxd := True
io.tx := uartCtrl.io.uart.txd
On the contrary, if you are using rxd
pin only:
val uartCtrl = UartCtrl(
config = UartCtrlInitConfig(
baudrate = 115200,
dataLength = 7,
parity = UartParityType.NONE,
stop = UartStopType.ONE
),
readonly = true
)
Example with test bench
Here is a top level example that does the followings things:
Instantiate
UartCtrl
and set its configuration to 921600 baud/s, no parity, 1 stop bit.Each time a byte is received from the UART, it writes it on the leds output.
Every 2000 cycles, it sends the switches input value to the UART.
case class UartCtrlUsageExample() extends Component {
val io = new Bundle {
val uart = master(Uart())
val switches = in Bits(8 bits)
val leds = out Bits(8 bits)
}
val uartCtrl = new UartCtrl()
// set config manually to show that this is still OK
uartCtrl.io.config.setClockDivider(921600)
uartCtrl.io.config.frame.dataLength := 7 // 8 bits
uartCtrl.io.config.frame.parity := UartParityType.NONE
uartCtrl.io.config.frame.stop := UartStopType.ONE
uartCtrl.io.uart <> io.uart
// Assign io.led with a register loaded each time a byte is received
io.leds := uartCtrl.io.read.toReg()
// Write the value of switch on the uart each 2000 cycles
val write = Stream(Bits(8 bits))
write.valid := CounterFreeRun(2000).willOverflow
write.payload := io.switches
write >-> uartCtrl.io.write
}
object UartCtrlUsageExample extends App {
SpinalConfig(
defaultClockDomainFrequency = FixedFrequency(100 MHz)
).generateVhdl(UartCtrlUsageExample())
}
Here you can get a simple VHDL testbench for this small UartCtrlUsageExample
.
Bonus: Having fun with Stream
If you want to queue data received from the UART:
val queuedReads = uartCtrl.io.read.toStream.queue(16)
If you want to add a queue on the write interface and do some flow control:
val writeCmd = Stream(Bits(8 bits))
val stopIt = Bool()
writeCmd.queue(16).haltWhen(stopIt) >> uartCtrl.io.write
If you want to send a 0x55 header before sending the value of switches, you can replace the write generator of the preceding example by:
val write = Stream(Fragment(Bits(8 bits)))
write.valid := CounterFreeRun(4000).willOverflow
write.fragment := io.switches
write.last := True
write.stage().insertHeader(0x55).toStreamOfFragment >> uartCtrl.io.write